﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;

namespace System.Windows.Art
{
    public static class ColorSpaceConverter
    {
        private const Double LabSigma = 6.0 / 29.0, LabCubicSigma = 2.16 / 243.89;

        private const Double ReferenceX = m11 + m12 + m13;
        private const Double ReferenceY = 1.0; //m21 + m22 + m23;
        private const Double ReferenceZ = m31 + m32 + m33;

        private const Double m11 = 0.412424, m12 = 0.357579, m13 = 0.180464;
        private const Double m21 = 0.212656, m22 = 0.715158, m23 = 0.072186;
        private const Double m31 = 0.019332, m32 = 0.119193, m33 = 0.950444;

        private const Double n11 = 3.24070785, n12 = -1.53725899, n13 = -0.498569642;
        private const Double n21 = -0.969257341, n22 = 1.87599528, n23 = 0.0415547483;
        private const Double n31 = 0.0556364458, n32 = -0.203996464, n33 = 1.05706945;

        private const Double ScNonLinearScale = 1.055, ScNonLinearShift = 0.055;

        public static Double ScRgbCurve(Double value)
        {
            if (value < 1.0 / 25.5) return value * 25.5 * 24.865 / 8192.0;
            else return Math.Pow((value + ColorSpaceConverter.ScNonLinearShift) / ColorSpaceConverter.ScNonLinearScale, 2.4);
        }

        public static Double InverseScRgbCurve(Double value)
        {
            if (value < 0) return 0.0;
            else if (value < 24.865 / 8192.0) return value * 8192.0 / (24.865 * 25.5);
            else if (value < 1.0) return ColorSpaceConverter.ScNonLinearScale * Math.Pow(value, 1 / 2.4) - ColorSpaceConverter.ScNonLinearShift;
            else return 1.0;
        }

        public static RgbColor ToRgb(this Color value)
        {
            return (RgbColor)value;
        }

        public static XYZColor ToXYZ(this Color value)
        {
            Double x = m11 * value.ScR + m12 * value.ScG + m13 * value.ScB;
            Double y = m21 * value.ScR + m22 * value.ScG + m23 * value.ScB;
            Double z = m31 * value.ScR + m32 * value.ScG + m33 * value.ScB;

            return XYZColor.FromScXYZ(value.ScA, x, y, z);
        }

        public static Color ToColor(XYZColor value)
        {
            Double scR = n11 * value.ScX + n12 * value.ScY + n13 * value.ScZ;
            Double scG = n21 * value.ScX + n22 * value.ScY + n23 * value.ScZ;
            Double scB = n31 * value.ScX + n32 * value.ScY + n33 * value.ScZ;

            return Color.FromScRgb((Single)value.ScAlpha, (Single)scR, (Single)scG, (Single)scB);
        }

        public static Color ToColor(LabColor value)
        {
            return ColorSpaceConverter.ToColor(value.ToXYZ());
        }

        public static LabColor ToLab(this Color value)
        {
            return value.ToXYZ().ToLab();
        }

        public static LabColor ToLab(this XYZColor value)
        {
            var x = value.ScX / ColorSpaceConverter.ReferenceX;
            var y = value.ScY / ColorSpaceConverter.ReferenceY;
            var z = value.ScZ / ColorSpaceConverter.ReferenceZ;

            x = (x > ColorSpaceConverter.LabCubicSigma) ? Math.Pow(x, 1 / 3.0) : (x * 841.0 / 108.0 + 4.0 / 29.0);
            y = (y > ColorSpaceConverter.LabCubicSigma) ? Math.Pow(y, 1 / 3.0) : (y * 841.0 / 108.0 + 4.0 / 29.0);
            z = (z > ColorSpaceConverter.LabCubicSigma) ? Math.Pow(z, 1 / 3.0) : (z * 841.0 / 108.0 + 4.0 / 29.0);

            var l = 1.160 * y - 0.160;
            var a = (x - y) * 500.0 / 255.0;
            var b = (y - z) * 200.0 / 255.0;

            return LabColor.FromScLab(value.ScAlpha, l, a, b);
        }

        public static XYZColor ToXYZ(this LabColor value)
        {
            var y = (value.ScLightness + 0.160) / 1.160;
            var x = y + value.ScA * 0.510; // 255÷500
            var z = y - value.ScB * 1.275; // 255÷200

            x = ColorSpaceConverter.ReferenceX * ((x > ColorSpaceConverter.LabSigma) ? x * x * x : (x - 4.0 / 29.0) * 108.0 / 841.0);
            y = ColorSpaceConverter.ReferenceY * ((y > ColorSpaceConverter.LabSigma) ? y * y * y : (y - 4.0 / 29.0) * 108.0 / 841.0);
            z = ColorSpaceConverter.ReferenceZ * ((z > ColorSpaceConverter.LabSigma) ? z * z * z : (z - 4.0 / 29.0) * 108.0 / 841.0);

            return XYZColor.FromScXYZ(value.ScAlpha, x, y, z);
        }

        public static HsvColor ToHsv(this Color value)
        {
            Double minRgb, maxRgb;
            Double red = value.R / 255.0, green = value.G / 255.0, blue = value.B / 255.0;
            ColorSpaceConverter.MinMax(red, green, blue, out minRgb, out maxRgb);

            var delta = maxRgb - minRgb;
            Double h = 0.0, s = 0.0, v = maxRgb;
            if (maxRgb != 0) s = delta / maxRgb;

            if (delta != 0)
            {
                if (red == maxRgb) h = (green - blue) / delta;
                else if (green == maxRgb) h = 2.0 + (blue - red) / delta;
                else if (blue == maxRgb) h = 4.0 + (red - green) / delta;
            }

            h /= 6.0;
            if (h < 0) h++;

            return HsvColor.FromScHsv(value.ScA, h, s, ColorSpaceConverter.ScRgbCurve(v));
        }

        public static HslColor ToHsl(this Color value)
        {
            Double minRgb, maxRgb;
            Double red = value.R / 255.0, green = value.G / 255.0, blue = value.B / 255.0;
            ColorSpaceConverter.MinMax(red, green, blue, out minRgb, out maxRgb);

            var delta = maxRgb - minRgb;
            Double h = 0.0, s = 0.0, l = (maxRgb + minRgb) / 2.0;
            if (l < 0.5) s = (l == 0) ? 0.0 : delta / (maxRgb + minRgb);
            else s = (l == 1) ? 0.0 : delta / (2.0 - maxRgb - minRgb);

            if (delta != 0)
            {
                if (red == maxRgb) h = (green - blue) / delta;
                else if (green == maxRgb) h = 2.0 + (blue - red) / delta;
                else if (blue == maxRgb) h = 4.0 + (red - green) / delta;
            }

            h /= 6.0;
            if (h < 0) h++;

            return HslColor.FromScHsl(value.ScA, h, s, ColorSpaceConverter.ScRgbCurve(l));
        }

        public static Color ToColor(HsvColor value)
        {
            if (value.ScValue == 0) return Color.FromScRgb((Single)value.ScAlpha, 0F, 0F, 0F);
            else
            {
                Double maxRgb = ColorSpaceConverter.InverseScRgbCurve(value.ScValue), minRgb = maxRgb * (1 - value.ScSaturation);
                var delta = maxRgb - minRgb;

                Double r = 0.0, g = 0.0, b = 0.0;
                Double h = value.ScHue * 6;
                if (h < 1) { r = maxRgb; b = minRgb; g = minRgb + h * delta; }
                else if (h < 2) { g = maxRgb; b = minRgb; r = maxRgb - (h - 1) * delta; }
                else if (h < 3) { g = maxRgb; r = minRgb; b = minRgb + (h - 2) * delta; }
                else if (h < 4) { b = maxRgb; r = minRgb; g = maxRgb - (h - 3) * delta; }
                else if (h < 5) { b = maxRgb; g = minRgb; r = minRgb + (h - 4) * delta; }
                else if (h < 6) { r = maxRgb; g = minRgb; b = maxRgb - (h - 5) * delta; }

                return Color.FromScRgb((Single)value.ScAlpha, (Single)ColorSpaceConverter.ScRgbCurve(r), (Single)ColorSpaceConverter.ScRgbCurve(g), (Single)ColorSpaceConverter.ScRgbCurve(b));
            }
        }

        public static Color ToColor(HslColor value)
        {
            if (value.ScLightness == 0) return Color.FromScRgb((Single)value.ScAlpha, 0F, 0F, 0F);
            else
            {
                var l = ColorSpaceConverter.InverseScRgbCurve(value.ScLightness);
                var delta = (l < 0.5) ? 2.0 * value.ScSaturation * l : 2.0 * value.ScSaturation * (1 - l);
                var minRgb = l - delta / 2.0;
                var maxRgb = l + delta / 2.0;

                Double r = 0.0, g = 0.0, b = 0.0;
                Double h = value.ScHue * 6;
                if (h < 1) { r = maxRgb; b = minRgb; g = minRgb + h * delta; }
                else if (h < 2) { g = maxRgb; b = minRgb; r = maxRgb - (h - 1) * delta; }
                else if (h < 3) { g = maxRgb; r = minRgb; b = minRgb + (h - 2) * delta; }
                else if (h < 4) { b = maxRgb; r = minRgb; g = maxRgb - (h - 3) * delta; }
                else if (h < 5) { b = maxRgb; g = minRgb; r = minRgb + (h - 4) * delta; }
                else if (h < 6) { r = maxRgb; g = minRgb; b = maxRgb - (h - 5) * delta; }

                return Color.FromScRgb((Single)value.ScAlpha, (Single)ColorSpaceConverter.ScRgbCurve(r), (Single)ColorSpaceConverter.ScRgbCurve(g), (Single)ColorSpaceConverter.ScRgbCurve(b));
            }
        }








        private static void MinMax(Double r, Double g, Double b, out Double minRgb, out Double maxRgb)
        {
            minRgb = r;
            maxRgb = r;
            if (r > g)
            {
                // R > G > B
                if (g > b) minRgb = b;
                // R > B > G
                else if (r > b) minRgb = g;
                // B > R > G
                else
                {
                    minRgb = g;
                    maxRgb = b;
                }
            }
            else if (g > b)
            {
                // G > R > B
                if (r > b)
                {
                    minRgb = b;
                    maxRgb = g;
                }
                // G > B > R
                else maxRgb = g;
            }
            // B > G > R
            else maxRgb = b;
        }
    }
}
